package aceim.protocol.snuk182.icq.inner.dataprocessing; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.net.UnknownHostException; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import aceim.protocol.snuk182.icq.inner.ICQConstants; import aceim.protocol.snuk182.icq.inner.ICQException; import aceim.protocol.snuk182.icq.inner.ICQServiceInternal; import aceim.protocol.snuk182.icq.inner.ICQServiceResponse; import aceim.protocol.snuk182.icq.inner.dataentity.Flap; import aceim.protocol.snuk182.icq.inner.dataentity.ICQOnlineInfo; import aceim.protocol.snuk182.icq.inner.dataentity.ServiceRedirect; import aceim.protocol.snuk182.icq.inner.dataentity.Snac; import aceim.protocol.snuk182.icq.inner.dataentity.TLV; import aceim.protocol.snuk182.icq.utils.ProtocolUtils; public class BuddyIconEngine { private ICQServiceInternal service; private ServiceRedirect serviceInfo; private IconRunnableService runnableService; private Set<String> requests = Collections.synchronizedSet(new HashSet<String>()); private short flapSeqNumber = 0; private static final byte CONNSTATE_DISCONNECTED = 0; private static final byte CONNSTATE_CONNECTING = 1; private static final byte CONNSTATE_CONNECTED = 2; private volatile byte connectState = CONNSTATE_DISCONNECTED; public short getFlapSeqNumber() { if (flapSeqNumber>=0x8000){ flapSeqNumber = 0; }; return flapSeqNumber++; } private final List<Flap> packets = Collections.synchronizedList(new LinkedList<Flap>()); //public Flap buffer = null; public BuddyIconEngine(ICQServiceInternal icqServiceInternal){ this.service = icqServiceInternal; } protected void forceFlapProcess(){ synchronized(packets){ while(packets.size()>0){ Flap flap = packets.remove(0); internalFlapMap(flap); } } } public void requestServiceServerUrl(){ Flap flap = new Flap(); flap.channel = ICQConstants.FLAP_CHANNELL_DATA; Snac data = new Snac(); data.serviceId = ICQConstants.SNAC_FAMILY_GENERIC; data.subtypeId = ICQConstants.SNAC_GENERIC_NEWSERVICEREQUEST; data.requestId = ICQConstants.SNAC_GENERIC_NEWSERVICEREQUEST; byte[] familyId = ProtocolUtils.short2ByteBE(ICQConstants.SNAC_FAMILY_SERVERSTOREDBUDDYICON); data.plainData = familyId; flap.data = data; service.getRunnableService().sendToSocket(flap); } public void serviceServerURLResponse(ServiceRedirect serviceInfo){ this.setServiceInfo(serviceInfo); service.log("got icon service "+serviceInfo.serviceServerURL); startRunnableService(); } private void startRunnableService(){ runnableService = new IconRunnableService(serviceInfo.serviceServerURL); runnableService.start(); } private void internalFlapMap(Flap flap){ if (flap == null) return; switch (flap.channel){ case ICQConstants.FLAP_CHANNELL_START: runnableService.sendToSocket(sendCookie()); break; case ICQConstants.FLAP_CHANNELL_DATA: internalSnacMap(flap.data); break; } } private void internalSnacMap(Snac data){ switch(data.getServiceId()){ case ICQConstants.SNAC_FAMILY_GENERIC: switch (data.subtypeId){ case ICQConstants.SNAC_GENERIC_SERVERSUPPORTEDFAMILIES: connectState = CONNSTATE_CONNECTED; /*if (buffer!=null){ runnableService.sendToSocket(buffer); buffer = null; }*/ forceIconRequests(); break; } break; case ICQConstants.SNAC_FAMILY_SERVERSTOREDBUDDYICON: switch (data.subtypeId){ case ICQConstants.SNAC_SERVERSTOREDBUDDYICON_BUDDYICONRES2: forceIconRequests(); parseIconData(data.plainData); break; case ICQConstants.SNAC_SERVERSTOREDBUDDYICON_ICONUPLOADRES: parseIconUploadResponse(data.plainData); service.getSSIEngine().newIcon = null; break; } break; } } private void parseIconUploadResponse(byte[] plainData) { short failReason = ProtocolUtils.bytes2ShortBE(plainData, 0); short resultCode = ProtocolUtils.bytes2ShortBE(plainData, 2); switch(resultCode) { case 0x101: if (failReason == 0) { byte[] hash = new byte[plainData[4]]; System.arraycopy(plainData, 5, hash, 0, hash.length); service.getServiceResponse().respond(ICQServiceResponse.RES_SAVEIMAGEFILE, service.getSSIEngine().newIcon, service.getOnlineInfo().uin, hash); } else { service.log("Error uploading icon, code is " + failReason); } break; default: service.log("Error uploading icon, code is " + failReason + " " + resultCode); } } private void parseIconData(byte[] plainData){ if (plainData==null) return; byte uinLength = plainData[0]; byte[] uinBytes = new byte[uinLength]; System.arraycopy(plainData, 1, uinBytes, 0, uinLength); String uin; uin = ProtocolUtils.getEncodedString(uinBytes); byte hashLength = plainData[uinLength+4]; byte[] hash = new byte[hashLength]; System.arraycopy(plainData, uinLength+5, hash, 0, hashLength); int toSkip = hashLength*2 + 5 + 5 + uinLength; int iconLength = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(plainData, toSkip)); byte[] iconBytes = new byte[iconLength]; System.arraycopy(plainData, toSkip+2, iconBytes, 0, iconBytes.length); service.getServiceResponse().respond(ICQServiceResponse.RES_SAVEIMAGEFILE, iconBytes, uin, hash); } private void forceIconRequests(){ while (requests.size() > 0) { synchronized (requests) { Iterator<String> i = requests.iterator(); while (i.hasNext()) { String uin = i.next(); ICQOnlineInfo info = null; if (uin.equals(service.getUn())) { info = service.getOnlineInfo(); } if (info == null) { info = service.getBuddyList().getByUin(uin); } if (info != null && info.iconData != null && info.iconData.iconId == 1 && info.iconData.flags == 1) { runnableService.sendToSocket(makeIconRequest(info)); } i.remove(); } } } if (service.getSSIEngine().newIcon != null) { runnableService.sendToSocket(makeIconUploadRequest(service.getSSIEngine().newIcon)); //service.getSSIEngine().newIcon = null; } //disconnect(); } public synchronized void requestIconUpload(byte[] bytes) { if (bytes == null) return; service.getSSIEngine().newIcon = bytes; service.log(" attempt icon upload request " + bytes.length); proceed(); } public synchronized void requestIcon(String uin){ service.log(" attempt icon request for " + uin); requests.add(uin); proceed(); } private void proceed() { while (service.getCurrentState() != ICQServiceInternal.STATE_CONNECTED && service.getCurrentState() != ICQServiceInternal.STATE_DISCONNECTED) { service.log("waiting for main service to get connected"); try { Thread.sleep(1000); } catch (InterruptedException e) {} } if (service.getCurrentState() == ICQServiceInternal.STATE_DISCONNECTED) { service.log("Service disconnected - cancel icon request"); requests.clear(); } else if (connectState == CONNSTATE_DISCONNECTED){ service.log("icon service get server"); connectState = CONNSTATE_CONNECTING; requestServiceServerUrl(); } else if (connectState == CONNSTATE_CONNECTED){ forceIconRequests(); } } private Flap sendCookie(){ service.log("--- send icon cookie"); Flap flap = new Flap(); flap.channel = ICQConstants.FLAP_CHANNELL_START; flap.sequenceNumber = getFlapSeqNumber(); TLV[] tlvs = new TLV[2]; tlvs[0] = new TLV((short)0x0, new byte[]{0x00, 0x01}); tlvs[1] = new TLV(ICQConstants.TLV_AUTHCOOKIE, serviceInfo.authCookie); flap.tlvData = tlvs; return flap; //service.getRunnableService().sendToSocket(flap); } private Flap makeIconUploadRequest(byte[] image) { Flap flap = new Flap(); flap.channel = ICQConstants.FLAP_CHANNELL_DATA; flap.sequenceNumber = getFlapSeqNumber(); Snac data = new Snac(); data.serviceId = ICQConstants.SNAC_FAMILY_SERVERSTOREDBUDDYICON; data.subtypeId = ICQConstants.SNAC_SERVERSTOREDBUDDYICON_ICONUPLOAD; data.requestId = ICQConstants.SNAC_SERVERSTOREDBUDDYICON_ICONUPLOAD; byte[] plainData = new byte[2+2+image.length]; System.arraycopy(ProtocolUtils.short2ByteBE((short) 1), 0, plainData, 0, 2); System.arraycopy(ProtocolUtils.short2ByteBE((short) image.length), 0, plainData, 2, 2); System.arraycopy(image, 0, plainData, 4, image.length); data.plainData = plainData; flap.data = data; return flap; } private Flap makeIconRequest(ICQOnlineInfo info){ service.log("make icon request " + info.uin); Flap flap = new Flap(); flap.channel = ICQConstants.FLAP_CHANNELL_DATA; flap.sequenceNumber = getFlapSeqNumber(); Snac data = new Snac(); data.serviceId = ICQConstants.SNAC_FAMILY_SERVERSTOREDBUDDYICON; data.subtypeId = ICQConstants.SNAC_SERVERSTOREDBUDDYICON_BUDDYICONREQ2; data.requestId = ICQConstants.SNAC_SERVERSTOREDBUDDYICON_BUDDYICONREQ2; byte[] uinBytes; try { uinBytes = info.uin.getBytes("ASCII"); } catch (UnsupportedEncodingException e) { uinBytes = info.uin.getBytes(); } byte[] plainData = new byte[6+uinBytes.length+info.iconData.hash.length]; plainData[0]= (byte) uinBytes.length; System.arraycopy(uinBytes, 0, plainData, 1, uinBytes.length); plainData[uinBytes.length+1] = 0x01; /*System.arraycopy(Utils.short2ByteBE((short) 0x01), 0, plainData, uinBytes.length+2, 2); plainData[uinBytes.length+4] = 0x01;*/ System.arraycopy(ProtocolUtils.short2ByteBE(info.iconData.iconId), 0, plainData, uinBytes.length+2, 2); plainData[uinBytes.length+4] = info.iconData.flags; plainData[uinBytes.length+5] = (byte) info.iconData.hash.length; System.arraycopy(info.iconData.hash, 0, plainData, uinBytes.length+6, info.iconData.hash.length); data.plainData = plainData; flap.data = data; return flap; } public void setServiceInfo(ServiceRedirect serviceInfo){ this.serviceInfo = serviceInfo; } public ServiceRedirect getServiceInfo() { return serviceInfo; } class IconRunnableService extends Thread { String host; int port; Socket socket; //public volatile boolean connected = true; public IconRunnableService(String url){ if (url.indexOf(":")>-1){ String[] ur = url.split(":"); host = ur[0]; port = Integer.parseInt(ur[1]); } else { host = url; port = service.getLoginPort(); } setName("ICQ icon service"); } @Override public void run() { try { socket = new Socket(host, port); } catch (UnknownHostException e) { service.log(e.getLocalizedMessage()); } catch (IOException e) { service.log(e.getLocalizedMessage()); } getDataFromSocket(); } private boolean getDataFromSocket(){ byte[] tail = null; int read = 0; int tailLength = 0; while (socket!=null && socket.isConnected() && connectState!=CONNSTATE_DISCONNECTED){ try { InputStream is = socket.getInputStream(); if (is.available()>0){ Thread.sleep(200); if (tail == null){ byte[] head = new byte[6]; is.read(head, 0, 6); read = 0; tailLength = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(head, 4)); tail = new byte[6+tailLength]; System.arraycopy(head, 0, tail, 0, 6); read += is.read(tail, 6, tailLength); service.log("icon engine get "+ProtocolUtils.getSpacedHexString(tail)); if (read<tailLength){ continue; } }else{ read += is.read(tail, 6+read, tailLength-read); if (read<tailLength){ continue; } } Flap flap = service.getDataParser().parseFlap(tail); synchronized(packets){ packets.add(flap); } try { forceFlapProcess(); } catch (Exception e) { packets.remove(flap); service.log(e); } tail = null; } else { Thread.sleep(300); } } catch (Exception e) { service.log(e); } } connectState = CONNSTATE_DISCONNECTED; service.log("icon service disconnected"); return true; } public boolean sendToSocket(Flap flap){ try { if (socket == null){ throw new IOException("Socket is null"); } OutputStream os = socket.getOutputStream(); final byte[] out = service.getDataParser().flap2Bytes(flap); service.log("icon engine sent "+ProtocolUtils.getSpacedHexString(out)); synchronized(os){ os.write(out); } } catch (IOException e) { service.log(e); socket = null; disconnect(); proceed(); } catch (ICQException e) { service.log(e.getLocalizedMessage()); } return true; } } public void disconnect() { connectState = CONNSTATE_DISCONNECTED; packets.clear(); } }